package com.oschina.bottle.bus;

import com.google.common.eventbus.AsyncEventBus;
import com.google.common.eventbus.EventBus;
import com.google.common.eventbus.Subscribe;
import org.springframework.aop.support.AopUtils;
import org.springframework.beans.BeansException;
import org.springframework.beans.factory.BeanFactory;
import org.springframework.beans.factory.BeanFactoryAware;
import org.springframework.beans.factory.SmartInitializingSingleton;
import org.springframework.beans.factory.config.BeanPostProcessor;
import org.springframework.core.Ordered;
import org.springframework.core.annotation.AnnotationUtils;
import org.springframework.util.Assert;
import org.springframework.util.ReflectionUtils;

import java.util.ArrayList;
import java.util.List;

/**
 * Subscribe 解析器
 *
 * @author <a href="mailto:zhongchao@gegejia.com">zhong</a>
 * @version 1.0 2016/12/30
 * @since 1.0
 */
public class SubscribeAnnotationBeanPostProcessor implements BeanFactoryAware, BeanPostProcessor,
    Ordered, SmartInitializingSingleton {

    private BeanFactory beanFactory;

    private AsyncEventBus asyncEventBus;

    private EventBus eventBus;

    private List<Object> beans = new ArrayList<>();

    @Override
    public void afterSingletonsInstantiated() {
        if (beanFactory.containsBean(EventBusConfigUtils.SYNC_EVENT_BUS_BEAN_NAME)) {

            this.eventBus = beanFactory
                .getBean(EventBusConfigUtils.SYNC_EVENT_BUS_BEAN_NAME, EventBus.class);
        }

        if (beanFactory.containsBean(EventBusConfigUtils.ASYNC_EVENT_BUS_BEAN_NAME)) {
            this.asyncEventBus = beanFactory
                .getBean(EventBusConfigUtils.ASYNC_EVENT_BUS_BEAN_NAME, AsyncEventBus.class);
        }

        for (Object bean : beans) {
            if (asyncEventBus != null) {
                this.asyncEventBus.register(bean);
            }

            if (eventBus != null) {
                this.eventBus.register(bean);
            }
        }

        beans.clear();
    }

    @Override
    public Object postProcessBeforeInitialization(Object bean, String beanName)
        throws BeansException {
        return bean;
    }

    @Override
    public Object postProcessAfterInitialization(Object bean, String beanName)
        throws BeansException {
        Class<?> targetClass = AopUtils.getTargetClass(bean);

        ReflectionUtils.doWithMethods(targetClass, method -> {
            Subscribe subscribe = AnnotationUtils.findAnnotation(method, Subscribe.class);
            if (subscribe != null) {
                if (!beans.contains(bean)) {
                    beans.add(bean);
                }
            }
        });
        return bean;
    }

    @Override
    public void setBeanFactory(BeanFactory beanFactory)
        throws BeansException {
        this.beanFactory = beanFactory;
    }

    @Override
    public int getOrder() {
        return LOWEST_PRECEDENCE;
    }
}
